﻿using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using ProjectAutumn.Graphics;
using ProjectAutumn.Input;
using ProjectAutumn.Window;

namespace ProjectAutumn
{
    public static class Utility
    {
        private static OperatingSystem os = (OperatingSystem)255;
        [DllImport("libc", EntryPoint = "uname")]
        private static extern void Uname(out Utsname unameStruct);
        public static bool IsX64() { return IntPtr.Size == 8; }
        public static OperatingSystem GetOS()
        {
            if (os != (OperatingSystem)255) return os;
            switch (Environment.OSVersion.Platform)
            {
                case PlatformID.Win32NT:
                    switch (Environment.OSVersion.Version.Major)
                    {
                        case 5:
                            os = OperatingSystem.WindowsXP;
                            break;
                        case 6:
                            os = Environment.OSVersion.Version.Build == 7600 ? OperatingSystem.Windows7 : OperatingSystem.WindowsVista;
                            break;
                        case 7:
                            os = OperatingSystem.Windows7;
                            break;
                        default:
                            goto default;
                    }
                    break;
                case PlatformID.WinCE:
                    os = OperatingSystem.WindowsCE;
                    break;
                case PlatformID.Unix:
                    string kernelName = DetectUnixKernel();
                    switch (kernelName)
                    {
                        case null:
                        case "":
                            os = OperatingSystem.Other;
                            break;
                        case "Linux":
                            os = OperatingSystem.Linux;
                            break;
                        case "Darwin":
                            os = OperatingSystem.Mac;
                            break;
                        default:
                            os = OperatingSystem.Unix;
                            break;
                    }
                    break;
                case PlatformID.Xbox:
                    os = OperatingSystem.Xbox;
                    break;
                case PlatformID.MacOSX:
                    os = OperatingSystem.Mac;
                    break;
                default:
                    os = OperatingSystem.Other;
                    break;
            }
            return os;
        }
        public static bool OnMono() { return Type.GetType("Mono.Runtime") != null; }
        private static string DetectUnixKernel()
        {
            Utsname uts;
            Uname(out uts);
            return uts.Sysname;
        }
        public static string GetOSName(OperatingSystem operatingSystem)
        {
            //TODO: Write the proper service pack and linux/mac version
            string osName;
            switch (operatingSystem)
            {
                case OperatingSystem.Linux:
                    osName = "Linux";
                    break;
                case OperatingSystem.Mac:
                    osName = "Mac OSX";
                    break;
                case OperatingSystem.Unix:
                    osName = "Unix";
                    break;
                case OperatingSystem.Windows7:
                    osName = "Windows 7";
                    break;
                case OperatingSystem.WindowsCE:
                    osName = "Windows CE";
                    break;
                case OperatingSystem.WindowsVista:
                    osName = "Windows Vista";
                    break;
                case OperatingSystem.WindowsXP:
                    osName = "Windows XP";
                    break;
                case OperatingSystem.Xbox:
                    osName = "Xbox";
                    break;
                default:
                    osName = "Other";
                    break;
            }
            if (IsX64())
                osName += " X64";
            return osName;
        }
        public static DirectXVersion GetDirectXVersion()
        {
            //TODO: Get a better way of finding out the installed DirectX version
            if (!IsWindows()) return DirectXVersion.None;
            string system32Dir = Environment.GetFolderPath(Environment.SpecialFolder.System) + '\\';
            if (File.Exists(system32Dir + "d3d11.dll")) return DirectXVersion.DirectX11;
            if (File.Exists(system32Dir + "d3d10.dll")) return DirectXVersion.DirectX10;
            return File.Exists(system32Dir + "d3d9.dll") ? DirectXVersion.DirectX9C : DirectXVersion.None;
        }
        internal static object LoadClass(string assembly, Type parent, params object[] args)
        {
            if (!File.Exists(assembly)) return null;
            Assembly loadedAssembly = Assembly.LoadFile(new FileInfo(assembly).FullName);
            try
            {
                return (from type in loadedAssembly.GetTypes() where type.BaseType != null && type.BaseType == parent select Activator.CreateInstance(type, args)).FirstOrDefault();
            }catch(TargetInvocationException e)
            {
                throw e.InnerException;
            }
        }
        public static bool IsWindows()
        {
            switch (GetOS())
            {
                case OperatingSystem.WindowsXP:
                case OperatingSystem.WindowsVista:
                case OperatingSystem.Windows7:
                case OperatingSystem.Windows2000:
                case OperatingSystem.Windows98:
                case OperatingSystem.WindowsME:
                case OperatingSystem.WindowsServer2003:
                case OperatingSystem.WindowsServer2007:
                    return true;
                default:
                    return false;
            }
        }
        public static bool MinimumWindows(OperatingSystem operatingSystem)
        {
            if (!IsWindows()) return false;
            OperatingSystem system = GetOS();
            switch(operatingSystem)
            {
                case OperatingSystem.Windows98: return true;
                case OperatingSystem.WindowsME: return system != OperatingSystem.Windows98;
                case OperatingSystem.Windows2000: return system != OperatingSystem.Windows98 && system != OperatingSystem.WindowsME;
                case OperatingSystem.WindowsServer2003:
                case OperatingSystem.WindowsXP:
                    return system != OperatingSystem.WindowsME && system != OperatingSystem.Windows98 && system != OperatingSystem.Windows2000;
                case OperatingSystem.WindowsServer2007:
                case OperatingSystem.WindowsVista:
                    return system == OperatingSystem.WindowsServer2007 || system == OperatingSystem.WindowsVista || system == OperatingSystem.Windows7;
                case OperatingSystem.Windows7:
                    return system == OperatingSystem.Windows7 || system == OperatingSystem.WindowsServer2007;
                default:
                    throw new ArgumentOutOfRangeException("operatingSystem");
            }
        }
        public static bool MinimumDirectX(DirectXVersion directXVersion)
        {
            switch (directXVersion)
            {
                case DirectXVersion.None:
                    return true;
                case DirectXVersion.DirectX9C:
                    return GetDirectXVersion() != DirectXVersion.None;
                case DirectXVersion.DirectX10:
                    return GetDirectXVersion() == DirectXVersion.DirectX10 ||
                           GetDirectXVersion() == DirectXVersion.DirectX11;
                case DirectXVersion.DirectX11:
                    return GetDirectXVersion() == DirectXVersion.DirectX11;
                default:
                    throw new ArgumentOutOfRangeException("directXVersion");
            }
        }
        public static bool IsValid(WindowSystems window)
        {
            OperatingSystem currentOS = GetOS();
            switch (window)
            {
                case WindowSystems.Null:
                    return true;
                case WindowSystems.GLUT:
                case WindowSystems.SDL:
                    if (WindowManager.Exists(window))
                        if ((IsWindows() || currentOS == OperatingSystem.Mac || currentOS == OperatingSystem.Linux || currentOS == OperatingSystem.Unix) &&
                           !IsX64())
                            return true;
                    return false;
                case WindowSystems.Form:
                    return WindowManager.Exists(window);
                default:
                    throw new ArgumentOutOfRangeException("window");
            }
        }
        public static bool IsValid(InputSystems input, WindowSystems window)
        {
            OperatingSystem currentOS = GetOS();
            switch (input)
            {
                case InputSystems.Null:
                    return true;
                case InputSystems.SDL:
                    return window == WindowSystems.SDL && InputManager.Exists(input) &&
                           (IsWindows() || currentOS == OperatingSystem.Mac || currentOS == OperatingSystem.Unix || currentOS == OperatingSystem.Linux) && !IsX64();
                case InputSystems.DirectInput:
                    return window == WindowSystems.Form &&
                           MinimumDirectX(DirectXVersion.DirectX9C) && !IsX64() && InputManager.Exists(input);
                default:
                    return false;
            }
        }
        public static bool IsValid(GraphicsSystems graphics, WindowSystems window)
        {
            OperatingSystem currentOS = GetOS();
            if (graphics != GraphicsSystems.Null &&
               !GraphicsManager.Exists(graphics)) return false;
            switch (graphics)
            {
                case GraphicsSystems.Null:
                    return true;
                case GraphicsSystems.DirectX9:
                    return window == WindowSystems.Form && IsWindows() && MinimumDirectX(DirectXVersion.DirectX9C);
                case GraphicsSystems.DirectX10:
                    return window == WindowSystems.Form && IsWindows() && MinimumDirectX(DirectXVersion.DirectX10);
                case GraphicsSystems.DirectX11:
                    return window == WindowSystems.Form && IsWindows() && MinimumDirectX(DirectXVersion.DirectX11);
                case GraphicsSystems.Direct2D:
                    return window == WindowSystems.Form && IsWindows() && MinimumDirectX(DirectXVersion.DirectX9C);
                case GraphicsSystems.SDL:
                    return window == WindowSystems.SDL &&
                           (IsWindows() || currentOS == OperatingSystem.Linux || currentOS == OperatingSystem.Unix || currentOS == OperatingSystem.Mac) && !IsX64();
                case GraphicsSystems.OpenGL:
                    return (window == WindowSystems.SDL || window == WindowSystems.Form ||
                            window == WindowSystems.GLUT) &&
                           (IsWindows() || currentOS == OperatingSystem.Linux || currentOS == OperatingSystem.Unix || currentOS == OperatingSystem.Mac);
                case GraphicsSystems.Form:
                    return window == WindowSystems.Form;
                default:
                    throw new ArgumentOutOfRangeException("graphics");
            }
        }
        public static string IsValidDetails(WindowSystems window)
        {
            if (IsValid(window)) return "Is Valid";
            OperatingSystem currentOS = GetOS();
            if (!WindowManager.Exists(window))
                return "Cannot find " + WindowManager.GetDll(window);
            switch (window)
            {
                case WindowSystems.GLUT:
                case WindowSystems.SDL:
                    if (!IsWindows() && currentOS != OperatingSystem.Mac && currentOS != OperatingSystem.Linux && currentOS != OperatingSystem.Unix)
                        return "Not a valid operating system.";
                    if (IsX64())
                        return "Can only run on a 32 bit operating system.";
                    throw new Exception();
                default:
                    throw new ArgumentOutOfRangeException("window");
            }
        }
        public static string IsValidDetails(InputSystems input, WindowSystems window)
        {
            if (IsValid(input, window)) return "Is Valid";
            OperatingSystem currentOS = GetOS();
            if (!InputManager.Exists(input))
                return "Cannot find " + InputManager.GetDll(input);
            switch (input)
            {
                case InputSystems.SDL:
                    if (window != WindowSystems.SDL)
                        return "Window System Must be SDL";
                    if (!IsWindows() && currentOS != OperatingSystem.Mac && currentOS != OperatingSystem.Linux && currentOS != OperatingSystem.Unix)
                        return "Not a valid operating system.";
                    if (IsX64())
                        return "Can only run on a 32 bit operating system.";
                    throw new Exception();
                case InputSystems.DirectInput:
                    if (window != WindowSystems.Form)
                        return "Window System Must be Windows Forms";
                    if (!IsWindows())
                        return "Not a valid operating system.";
                    if (!MinimumDirectX(DirectXVersion.DirectX9C))
                        return "Don't meet the minimum DirectX requirements.";
                    throw new Exception();
                default:
                    throw new ArgumentOutOfRangeException("input");
            }
        }
        public static string IsValidDetails(GraphicsSystems graphics, WindowSystems window)
        {
            if (IsValid(graphics, window)) return "Is Valid";
            OperatingSystem currentOS = GetOS();
            if (!GraphicsManager.Exists(graphics))
                return "Cannot find " + GraphicsManager.GetDll(graphics);
            switch (graphics)
            {
                case GraphicsSystems.DirectX9:
                    if (window != WindowSystems.Form)
                        return "Window System Must be Windows Forms";
                    if (!IsWindows())
                        return "Not a valid operating system.";
                    if (!MinimumDirectX(DirectXVersion.DirectX9C))
                        return "Don't meet the minimum DirectX requirements.";
                    throw new Exception();
                case GraphicsSystems.DirectX10:
                    if (window != WindowSystems.Form)
                        return "Window System Must be Windows Forms";
                    if (!IsWindows())
                        return "Not a valid operating system.";
                    if (!MinimumDirectX(DirectXVersion.DirectX10))
                        return "Don't meet the minimum DirectX requirements.";
                    throw new Exception();
                case GraphicsSystems.DirectX11:
                    if (window != WindowSystems.Form)
                        return "Window System Must be Windows Forms";
                    if (!IsWindows())
                        return "Not a valid operating system.";
                    if (!MinimumDirectX(DirectXVersion.DirectX11))
                        return "Don't meet the minimum DirectX requirements.";
                    throw new Exception();
                case GraphicsSystems.Direct2D:
                    if (window != WindowSystems.Form)
                        return "Window System Must be Windows Forms";
                    if (!IsWindows())
                        return "Not a valid operating system.";
                    if (!MinimumDirectX(DirectXVersion.DirectX9C))
                        return "Don't meet the minimum DirectX requirements.";
                    throw new Exception();
                case GraphicsSystems.SDL:
                    if (window != WindowSystems.SDL)
                        return "Window System Must be SDL";
                    if (!IsWindows() && currentOS != OperatingSystem.Mac && currentOS != OperatingSystem.Linux && currentOS != OperatingSystem.Unix)
                        return "Not a valid operating system.";
                    if (IsX64())
                        return "Can only run on a 32 bit operating system.";
                    throw new Exception();
                case GraphicsSystems.OpenGL:
                    if (window == WindowSystems.Null)
                        return "Window System Must not be null.";
                    if (!IsWindows() && currentOS != OperatingSystem.Mac && currentOS != OperatingSystem.Linux && currentOS != OperatingSystem.Unix)
                        return "Not a valid operating system.";
                    throw new Exception();
                case GraphicsSystems.Form:
                    if (window != WindowSystems.Form)
                        return "Window System Must be Windows Forms";
                    throw new Exception();
                default:
                    throw new ArgumentOutOfRangeException("graphics");
            }
        }
        #region Nested type: Utsname
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        private struct Utsname
        {
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
            public readonly string Sysname;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
            public readonly string Nodename;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
            public readonly string Release;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
            public readonly string Version;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
            public readonly string Machine;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1024)]
            public readonly string ExtraJustInCase;
        }
        #endregion
    }
}
